package org.hugoduncan.appam.component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URISyntaxException;

import javax.servlet.ServletException;

import org.apache.abdera.factory.Factory;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Link;
import org.apache.abdera.parser.Parser;
import org.apache.commons.io.IOUtils;
import org.apache.hivemind.ApplicationRuntimeException;
import org.hugoduncan.appam.model.Constants;
import org.hugoduncan.appam.model.EntryComponent;
import org.hugoduncan.appam.server.AppServletRequest;
import org.hugoduncan.appam.server.AppServletResponse;

/**
 * APP EntryComponent based on a filesystem object.
 * Their are actually two file system entries, one for
 * each of the Atom entry and the content.
 * @author duncanhugo
 *
 */
public class FileSystemEntry
    extends BaseEntryComponent
    implements EntryComponent
{
    private File root=null;

    public void setRoot(File root) {
        this.root = root;
    }

    
    @Override
    protected AppRequestContext createRequestContext() {
        return new FileSystemEntryContext();
    }


    @Override
    public Entry process(AppServletRequest request, AppServletResponse response)
        throws ServletException, IOException
    {
        if (root==null)
            throw new ApplicationRuntimeException("Root property not set");
        return super.process(request, response);
    }

    /**
     * Optimised getResource, as we do not need to parse the entry
     */
    @Override
    protected Entry getResource(AppServletRequest request,
            AppServletResponse response, AppRequestContext context)
            throws ServletException, IOException {
        
        File entryFile=getEntryFile(getEntryPath(request.getPathInfo()));
        Document<Entry> entryDocument=null;

        FileInputStream is=new FileInputStream(entryFile);
        try {
            entryDocument=Parser.INSTANCE.parse(is);
        }
        finally {
            is.close();
            is=null;
        }

        response.setContentType(Constants.SC_ATOM_CONTENT_TYPE);
        response.setCharacterEncoding("utf-8");
        response.setStatus(AppServletResponse.SC_OK);
        response.setContentLength((int)entryFile.length());
        try {
            entryDocument.writeTo(response.getWriter());
            response.flushBuffer();
        } catch (FileNotFoundException e) {
            response.sendError(AppServletResponse.SC_NOT_FOUND,
                               "No such entry");
        }
        return entryDocument.getRoot();
    }

    @Override
    protected Entry retrieveExistingEntry(AppServletRequest request,
            AppRequestContext context) throws IOException {
        
        FileSystemEntryContext fsContext=(FileSystemEntryContext) context;        
        File entryFile = getEntryFile(request, fsContext);
        
        return readEntry(entryFile);
    }


    @Override
    protected Entry retrieveExistingMediaLinkEntry(AppServletRequest request, AppRequestContext context)
        throws IOException {

        FileSystemEntryContext fsContext=(FileSystemEntryContext) context;        
        File contentFile = getContentFile(request, fsContext);
        if (contentFile==null)
            return null;
        File entryFile = translateContentFileToEntryFile(contentFile);
        
        Entry existingEntry=null;
        FileInputStream is=new FileInputStream(entryFile);
        try {
            
            Document<Entry> existingDocument=Parser.INSTANCE.parse(is);
            existingEntry=existingDocument.getRoot();
        }
        finally {
            is.close();
        }
        return existingEntry;
    }


    @Override
    protected Entry deleteExistingResource(AppServletRequest request,
            AppRequestContext context) throws IOException {

        FileSystemEntryContext fsContext=(FileSystemEntryContext) context;        
        File entryFile = getEntryFile(request, fsContext);
        File contentFile = getContentFile(request, fsContext);
        
        if (entryFile==null && contentFile!=null)
            entryFile=translateContentFileToEntryFile(contentFile);
        
        if (contentFile==null && entryFile!=null)
            contentFile=translateEntryFileToContentFile(entryFile);

        Entry entry=null;

        if (entryFile!=null && entryFile.exists())
        {
            Entry existingEntry=readEntry(entryFile);

            boolean entryOK=entryFile.delete();
            if (entryOK)
            {
                contentFile.delete();
                entry=existingEntry;
            }
            else
                log.error("Requested DELETE could not be performed");
        }
       return entry;
    }

    
    @Override
    protected void storeEntry(Entry entry, AppRequestContext context)
            throws IOException {

        FileSystemEntryContext fsContext=(FileSystemEntryContext) context;

        File entryFile=fsContext.getEntryFile();

        if (entryFile==null)
        {
            entryFile=fsContext.getContentFile();
            if (entryFile!=null)
                entryFile = translateContentFileToEntryFile(entryFile);
            else
                entryFile=generateEntryFile(entry);
        }
        fsContext.setEntryFile(entryFile);

        String editLink=getLocation()+getEntryUrl(entryFile);
        try {
            Link link=entry.getLink("edit");
            if (link==null)
                entry.addLink(editLink,"edit");
            else
                link.setHref(editLink);
        } catch (URISyntaxException e) {
            log.error("problem with edit link",e);
        }

        if (entryFile.exists())
        {
            File tmpEntry=File.createTempFile("atom",".tmp");
            FileOutputStream os=new FileOutputStream(tmpEntry);
            try {
                entry.getDocument().writeTo(os);
            } finally {
                os.flush();
                os.close();
            }
            if (entryFile.delete())
                tmpEntry.renameTo(entryFile);
            else
                log.error("Couldn't write updated entry file");
        }
        else
        {
            FileOutputStream os=new FileOutputStream(entryFile);
            try {
                entry.getDocument().writeTo(os);
            } finally {
                os.close();
            }
        }
    }

    @Override
    protected String storeMediaResource(
            AppServletRequest request, AppRequestContext context)
        throws IOException
    {
        FileSystemEntryContext fsContext=(FileSystemEntryContext) context;

        // get the file for the media resource
        File contentFile=fsContext.getContentFile();
        if (contentFile==null)
        {
            contentFile=fsContext.getEntryFile();
            if (contentFile!=null)
                contentFile = translateEntryFileToContentFile(contentFile);
            else
                contentFile = generateContentFile();
        }
        fsContext.setContentFile(contentFile);
        
        
        File tmp=File.createTempFile("atom",".tmp");
        FileOutputStream os=new FileOutputStream(tmp);
        try {
            IOUtils.copy(request.getReader(), os);
        }
        finally {
            os.flush();
            os.close();
        }

        if (!contentFile.exists() || contentFile.delete())
           tmp.renameTo(contentFile);
        
        return getContentUrl(contentFile);
    }

    @Override
    protected Entry createMediaLinkEntry(AppServletRequest request, AppRequestContext context) {
        Entry entry;
        String title=request.getTitle();
        if (title==null || title.length()==0)
            title="Untitled";

        entry=Factory.INSTANCE.newEntry();
        entry.setTitle(title);
        return entry;
    }


    /**
     * Translate a content file into it's related entry file
     * @param entryFile
     * @return
     */
    protected File translateContentFileToEntryFile(File contentFile) {
        File entryFile=new File(contentFile.getPath().replaceAll("\\.content", ".atom"));
        return entryFile;
    }

    /**
     * Translate an entry file into its related content file
     * @param contentFile
     * @return
     */
    protected File translateEntryFileToContentFile(File entryFile) {
        File contentFile=new File(entryFile.getPath().replaceAll("\\.atom", ".content"));
        return contentFile;
    }



    /**
     * Create an entry file in which to store the entry.
     * @param entry
     * @return
     * @throws IOException
     */
    protected File generateEntryFile(Entry entry) throws IOException
    {
        return File.createTempFile("entry", ".atom", root);
    }
    
    /**
     * Create a content file in which to store the media content.
     * @param entry
     * @return
     * @throws IOException
     */
    protected File generateContentFile() throws IOException {
        return  File.createTempFile("entry", ".content", root);
    }
    
    /**
     * Get the entry file from the entry's path
     * @param path
     * @return
     */
    protected File getEntryFile(String path)
    {
        if (!path.endsWith(".atom"))
            return null;
        return new File(root.getPath()+path);
    }

    /**
     * Get the content file from the entry's path
     * @param path
     * @return
     */
    protected File getContentFile(String path)
    {
        if (!path.endsWith(".content"))
            return null;
        return new File(root.getPath()+path);
    }


    /**
     * Obtain the local url of the entry
     * @param baseUrl
     * @param entryFile
     * @return
     */
    protected String getEntryUrl(File entryFile)
    {
        return "/"+entryFile.getName();
    }

    /**
     * Obtain the local url of the entry
     * @param baseUrl
     * @param entryFile
     * @return
     */
    protected String getContentUrl(File contentFile)
    {
        return "/"+contentFile.getName();
    }
    
    /**
     * Get the path for the resource from the request pathInfo
     * @param pathInfo
     * @return
     */
    private String getEntryPath(String pathInfo)
    {
        String path=pathInfo.substring(getLocation().length());
        return path.replace('/', File.separatorChar);
    }

    /**
     * Get the path for the media resource fromthe request pathInfo
     * @param pathInfo
     * @return
     */
    private String getContentPath(String pathInfo)
    {
        String path=pathInfo.substring(getLocation().length());
        return path.replace('/', File.separatorChar);
    }

    

    /**
     * Obtain the entry file from the request context or from the request path
     * @param request
     * @param fsContext
     * @return
     */
    private File getEntryFile(AppServletRequest request, FileSystemEntryContext fsContext) {
        File entryFile=fsContext.getEntryFile();
        if (entryFile==null) {
            entryFile=getEntryFile(getEntryPath(request.getPathInfo()));
            fsContext.setEntryFile(entryFile);
        }
        return entryFile;
    }
    
    /**
     * Obtain the entry file from the request context or from the request path
     * @param request
     * @param fsContext
     * @return
     */
    private File getContentFile(AppServletRequest request, FileSystemEntryContext fsContext) {
        File contentFile=fsContext.getContentFile();
        if (contentFile==null) {
            contentFile=getContentFile(getContentPath(request.getPathInfo()));
            if (contentFile==null)
            {
                File entryFile=getEntryFile(request,fsContext);
                if (entryFile!=null)
                    contentFile=translateEntryFileToContentFile(entryFile);
            }
            fsContext.setContentFile(contentFile);
        }
        return contentFile;
    }
       
    private Entry readEntry(File entryFile) throws IOException {
        Entry existingEntry=null;
        FileInputStream is=null;
        try {
            is=new FileInputStream(entryFile);
            Document<Entry> existingDocument=Parser.INSTANCE.parse(is);
            existingEntry=existingDocument.getRoot();
        } catch (FileNotFoundException e) {
            log.error("Could not read entry",e);
        }
        finally {
            if (is!=null)
                is.close();
        }
        return existingEntry;
    }


/*
    private void writeEntryFile(AppServletResponse response, File entryFile)
        throws FileNotFoundException, IOException
    {
        // write the response
        response.setContentType(Constants.SC_ATOM_CONTENT_TYPE);
        response.setCharacterEncoding("utf-8");

        response.setContentLength((int)entryFile.length());
        BufferedReader in = new BufferedReader(
            new InputStreamReader(new FileInputStream(entryFile)));
        IOUtils.copy(in, response.getWriter());
        in.close();
        response.flushBuffer();
    }

    private <T extends Element> void debugWrite(Document<T> document)
    {
        try {
            StringWriter sw=new StringWriter();
            document.writeTo(sw);
            log.debug(sw.toString());
        } catch (IOException e1) {
            log.error("Could not output document",e1);
        }
    }
    */
}
